/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// r_misc.c

#include "quakedef.h"

geo_t d3d_BoundingBox;
QSHADER d3d_BBoxShader;

class CMiscHandler : public CD3DHandler
{
public:
	void OnCreateDevice (void)
	{
		d3d_BoundingBox.CreateBox ();
		d3d_BBoxShader.CreateShaders (IDR_BBOXSHADER, "BBoxVS", NULL, "BBoxPS", INPUT_LAYOUT_XYZ);
	}

	void OnReleaseDevice (void)
	{
		d3d_BoundingBox.Release ();
	}
} d3d_MiscHandler;


void R_DrawBoundingBox (const float *mins, const float *maxs, const float *shadelight)
{
	// each bbox is defined by a fake entity that we send through our standard entity setup routine
	entity_t e;

	// compute scaling factor
	float boxscale[3] = {(maxs[0] - mins[0]) * 0.5f, (maxs[1] - mins[1]) * 0.5f, (maxs[2] - mins[2]) * 0.5f};

	// expand the bbox by 1 unit in all directions for drawing
	e.localmatrix.Load (
		boxscale[0] + 1, 0, 0, mins[0] + boxscale[0],
		0, boxscale[1] + 1, 0, mins[1] + boxscale[1],
		0, 0, boxscale[2] + 1, mins[2] + boxscale[2],
		0, 0, 0, 1
	);

	Vector3Copy (e.shadelight, shadelight);

	R_SetupObjectConstants (&e);

	d3d_Device->DrawIndexed (d3d_BoundingBox.NumIndexes, 0, 0);
	c_draw_call++;
}


void R_ShowBBoxes (void)
{
	UINT strides[] = {sizeof (float) * 3};
	UINT offsets[] = {0};
	ID3D10Buffer *Buffers[] = {d3d_BoundingBox.VertexBuffer};

	d3d_Device->IASetVertexBuffers (BUFFER_SLOT_LVOLUME, 1, Buffers, strides, offsets);
	d3d_Device->IASetIndexBuffer (d3d_BoundingBox.IndexBuffer, DXGI_FORMAT_R16_UINT, 0);
	d3d_Device->IASetPrimitiveTopology (D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	d3d_BBoxShader.Bind ();

	d3d_State->OMSetBlendState (d3d_BlendAlpha);
	d3d_State->OMSetDepthState (d3d_ZmodeNowrite);

	if (r_showbboxes->integer == 2 && sv.active)
	{
		for (int i = 1; i < sv.num_edicts; i++)
		{
			edict_t *ed = sv.edictpointers[i];

			if (ed->free) continue;
			if (ed == sv_player) continue;

			if (ed->v.mins[0] == ed->v.maxs[0] && ed->v.mins[1] == ed->v.maxs[1] && ed->v.mins[2] == ed->v.maxs[2])
			{
				// point entity
				float mins[3], maxs[3];

				Vector3Subtract (mins, ed->v.origin, float3 (2, 2, 2));
				Vector3Add (maxs, ed->v.origin, float3 (2, 2, 2));

				R_DrawBoundingBox (mins, maxs, float3 (0.2f, 0.4f, 0.3f));
			}
			else if (ed->v.modelindex)
			{
				// box entity with a model
				float mins[3], maxs[3];
				model_t *model = sv.models[(int) ed->v.modelindex];

				Vector3Add (mins, ed->v.origin, model->mins);
				Vector3Add (maxs, ed->v.origin, model->maxs);

				R_DrawBoundingBox (mins, maxs, float3 (0.3f, 0.2f, 0.4f));
			}
			else
			{
				// box entity without a model
				R_DrawBoundingBox (ed->v.mins, ed->v.maxs, float3 (0.4f, 0.2f, 0.3f));
			}
		}

		return;
	}

	for (int i = 0; i < MAX_DLIGHTS; i++)
	{
		dlight_t *dl = &cl_dlights[i];
		float mins[3], maxs[3];

		// this is not a valid dlight
		if (!dl->Alive ()) continue;

		// compute the bounding box
		Mod_BBoxFromSphere (dl->origin, dl->radius, mins, maxs);

		// and draw it
		R_DrawBoundingBox (mins, maxs, float3 (0.3f, 0.3f, 0.3f));
	}

	extern int r_numvisedicts;
	extern entity_t *r_visedicts[];

	for (int i = 0; i < r_numvisedicts; i++)
	{
		entity_t *e = r_visedicts[i];
		aliashdr_t *hdr;
		float mins[3], maxs[3];

		if (!e->model) continue;
		if (e == &cl.viewent) continue;

		if (e->model->type == mod_alias)
		{
			// retrieve the alias header
			hdr = e->model->aliashdr;

			// compute the bounding box
			Mod_ComputeAliasBBox (e, hdr, mins, maxs);

			// and draw it
			R_DrawBoundingBox (mins, maxs, float3 (0.4f, 0.3f, 0.2f));
		}
		else if (e->model->type == mod_brush)
		{
			// compute the bounding box
			Mod_ComputeBBox (&e->localmatrix, e->model->mins, e->model->maxs, mins, maxs);

			// and draw it
			R_DrawBoundingBox (mins, maxs, float3 (0.2f, 0.3f, 0.4f));
		}
	}
}


void RGBToYCoCg (float *rgb)
{
	float Co = rgb[0] - rgb[2];
	float t = rgb[2] + (Co * 0.5f);
	float Cg = rgb[1] - t;
	float Y = t + (Cg * 0.5f);

	Vector3Set (rgb, Y, Co, Cg);
}


void YCoCgToRGB (float *c)
{
	float t = c[0]  - (c[2] * 0.5f);
	float g = c[2] + t;
	float b = t  - (c[1] * 0.5f);
	float r = c[1] + b;

	Vector3Set (c, r, g, b);
}


// advanced each map change
// big objects are allowed to persist for 2 map changes before being destroyed
// smaller objects are allowed to persist for 4 map changes before being destroyed
// lightmaps must be destroyed after only one map change
int r_RegistrationSequence = 1;


/*
===============
R_Init
===============
*/
void R_Init (void)
{
	extern byte *hunk_base;

	Cmd_AddCommand ("timerefresh", R_TimeRefresh_f);
	Cmd_AddCommand ("pointfile", R_ReadPointFile_f);
	Cmd_AddCommand ("loadsky", Sky_LoadSkyBox_f);

	r_norefresh = Cvar_Get ("r_norefresh", "0");
	r_drawentities = Cvar_Get ("r_drawentities", "1");
	r_drawviewmodel = Cvar_Get ("r_drawviewmodel", "1");

#ifdef _DEBUG
	r_speeds = Cvar_Get ("r_speeds", "1");
#else
	r_speeds = Cvar_Get ("r_speeds", "0");
#endif

	r_fullbright = Cvar_Get ("r_fullbright", "0");
	r_lightmap = Cvar_Get ("r_lightmap", "0");
	r_lightscale = Cvar_Get ("r_lightscale", "1");
	r_shadows = Cvar_Get ("r_shadows", "0");

	// this one needs to reset vis too because a player could set r_novis then r_wateralpha
	r_wateralpha = Cvar_Get ("r_wateralpha", "1", 0, R_VisReset);

	r_dynamic = Cvar_Get ("r_dynamic", "1");
	r_novis = Cvar_Get ("r_novis", "0", 0, R_VisReset);
	r_draworder = Cvar_Get ("r_draworder", "0");
	r_lockpvs = Cvar_Get ("r_lockpvs", "0", 0, R_VisReset);
	r_lockfrustum = Cvar_Get ("r_lockfrustum", "0", 0, R_VisReset);
	r_clearcolor = Cvar_Get ("r_clearcolor", "2", CVAR_ARCHIVE);

	gl_finish = Cvar_Get ("gl_finish", "0");
	gl_clear = Cvar_Get ("gl_clear", "0");
	gl_cull = Cvar_Get ("gl_cull", "1");
	gl_polyblend = Cvar_Get ("gl_polyblend", "1");
	gl_nocolors = Cvar_Get ("gl_nocolors", "0");
	gl_doubleeyes = Cvar_Get ("gl_doubleeys", "1");
	gl_texture_anisotropy = Cvar_Get ("gl_texture_anisotropy", "1", CVAR_ARCHIVE);
	r_batchmode = Cvar_Get ("r_batchmode", "1");
	r_showbboxes = Cvar_Get ("r_showbboxes", "0");
	r_particle_size = Cvar_Get ("r_particle_size", "40", CVAR_ARCHIVE);

#ifdef _DEBUG
	r_realtime_lighting = Cvar_Get ("r_realtime_lighting", "1");
#else
	r_realtime_lighting = Cvar_Get ("r_realtime_lighting", "0");
#endif

	r_shadowmapdepthbias = Cvar_Get ("r_shadowmapdepthbias", "0.93");

	Cmd_AddCommand ("r_reload_rtlights", R_ReloadRTLights);

	Fog_Init ();
	R_InitParticles ();
}


/*
===============
R_TranslatePlayerSkin

Translates a skin texture by the per-player color lookup
===============
*/
void R_TranslatePlayerSkin (int playernum)
{
	/*
	int top = cl.scores[playernum].colors & 0xf0;
	int bottom = (cl.scores[playernum].colors & 15) << 4;
	entity_t *e = cl.entities[1 + playernum];
	model_t *model = e->model;
	aliashdr_t *hdr = NULL;

	if (!model) return;						// player doesn't have a model yet
	if (model->type != mod_alias) return;	// only translate skins on alias models

	hdr = model->aliashdr;

	// always recreate it fully if the texture size changes
	if (hdr->skinwidth != PlayerTextures[playernum].Width || hdr->skinheight != PlayerTextures[playernum].Height)
	{
		// clear the cached values so that it will recache
		SAFE_RELEASE (PlayerTextures[playernum].Texture);
		PlayerTextures[playernum].Top = -1;
		PlayerTextures[playernum].Bottom = -1;
	}

	// if the texture doesn't need to be recreated then don't recreate it
	if (PlayerTextures[playernum].Top == top && PlayerTextures[playernum].Bottom == bottom && PlayerTextures[playernum].Texture)
	{
		// Con_Printf (PRINT_DEFAULT, "not translating texture for player %i\n", playernum);
		return;
	}

	// create the texture if needed
	if (!PlayerTextures[playernum].Texture)
		PlayerTextures[playernum].Texture = D3D_CreateTexture (NULL, hdr->skinwidth, hdr->skinheight, TEX_MIPMAP);

	// if it created OK we fill it and mipmap it, using translated as the palette
	if (PlayerTextures[playernum].Texture)
	{
		IDirect3DSurface9 *surf = NULL;

		if (SUCCEEDED (PlayerTextures[playernum].Texture->GetSurfaceLevel (0, &surf)))
		{
			int i;
			RECT SrcRect = {0, 0, hdr->skinwidth, hdr->skinheight};
			byte *original = NULL;
			byte translate[256];
			unsigned translated[256];

			// build an identity table
			for (i = 0; i < 256; i++)
				translate[i] = i;

			// build the translation component of it
			for (i = 0; i < 16; i++)
			{
				if (top < 128)	// the artists made some backwards ranges.  sigh.
					translate[TOP_RANGE + i] = top + i;
				else translate[TOP_RANGE + i] = top + 15 - i;

				if (bottom < 128)
					translate[BOTTOM_RANGE + i] = bottom + i;
				else translate[BOTTOM_RANGE + i] = bottom + 15 - i;
			}

			// and translate it
			for (i = 0; i < 256; i++)
				translated[i] = d_8to24table_rgba[translate[i]];

			// locate the original skin pixels
			if (e->skinnum < 0 || e->skinnum >= hdr->numskins)
			{
				Con_Printf (PRINT_DEFAULT, "(%d): Invalid player skin #%d\n", playernum, e->skinnum);
				original = hdr->skins[0].skins[0].SkinTexels;
			}
			else original = hdr->skins[e->skinnum].skins[0].SkinTexels;

			D3DXLoadSurfaceFromMemory (
				surf,
				NULL,
				NULL,
				original,
				D3DFMT_P8,
				hdr->skinwidth,
				(PALETTEENTRY *) translated,
				&SrcRect,
				D3DX_FILTER_LINEAR,
				0
			);

			SAFE_RELEASE (surf);

			// mipmap the player texture
			D3DXFilterTexture (PlayerTextures[playernum].Texture, NULL, 0, D3DX_FILTER_LINEAR);

			// fill in the rest of the properties
			PlayerTextures[playernum].Width = hdr->skinwidth;
			PlayerTextures[playernum].Height = hdr->skinheight;
			PlayerTextures[playernum].Top = top;
			PlayerTextures[playernum].Bottom = bottom;
		}
	}
	*/
}


/*
===============
R_NewMap
===============
*/
void R_NewMap (void)
{
	memset (&r_worldentity, 0, sizeof (r_worldentity));
	r_worldentity.model = cl.worldmodel;

	// clear out efrags in case the level hasn't been reloaded
	for (int i = 0; i < cl.worldmodel->numleafs; i++)
		cl.worldmodel->leafs[i].efrags = NULL;

	// clear out player textures
	for (int i = 0; i < MAX_SCOREBOARD; i++)
	{
		SAFE_RELEASE (PlayerTextures[i].Texture);
		memset (&PlayerTextures[i], 0, sizeof (playertexture_t));
	}

	ref.ViewLeaf[0] = ref.ViewLeaf[1] = ref.ViewLeaf[2] = NULL;

	ref.FrameCount = 1;
	ref.VisFrameCount = 1;

	// unbind all state
	d3d_State->ClearState ();

	// build anything that needs to be built
	R_ClearParticles ();
	GL_BuildBrushSurfaces ();
	Sprite_CreateBuffers ();
	R_LoadMeshBuffers ();
	Sky_NewMap ();
	Fog_NewMap ();
	CL_NewTentsForNewMap ();

	// reload the lights
	if (r_realtime_lighting->boolean)
		R_ReloadRTLights ();

	// free any unused D3D objects
	D3D_FreeUnusedTextures ();

	// clear any outstanding centerprint (in case this didn't come from a SV_SpawnServer call, i.e it's a demo)
	scr.CentertimeOff = -1;

	// free any temp allocations made during loading
	qScratch->FreeToLowMark (0);

	// rearm the timers because this may have taken some time
	Host_RearmTimers ();

	// clear the input buffers in case anything built up while things were loading
	IN_ClearRawInputBuffer ();
	Key_ClearStates ();

	// go into game mode on entry
	Key_SetDest (key_game);

	// clear these because the old pvs and frustum will be completely invalid
	Cvar_SetValue (r_lockpvs, 0);
	Cvar_SetValue (r_lockfrustum, 0);

	// reload realtime lighting info
	R_LoadRealtimeLight ();

	// and reset the old view leafs to cause a PVS update on first entry
	R_VisReset ();

	// kill sounds
	S_ClearBuffer ();

	// clear shadow buffers
	R_ClearShadowBuffers ();

	// go to the next registration sequence
	r_RegistrationSequence++;
}


/*
====================
R_TimeRefresh_f

For program optimization
====================
*/
void R_TimeRefresh_f (void)
{
	/*
	GL_REMOVED
	int			i;
	float		start, stop, time;
	int			startangle;
	vrect_t		vr;

	glDrawBuffer (GL_FRONT);
	glFinish ();

	start = Sys_DoubleTime ();

	for (i = 0; i < 128; i++)
	{
		ref.ViewAngles[1] = i / 128.0 * 360.0;
		R_RenderView ();
	}

	glFinish ();
	stop = Sys_DoubleTime ();
	time = stop - start;
	Con_Printf (PRINT_DEFAULT, "%f seconds (%f fps)\n", time, 128 / time);

	glDrawBuffer (GL_BACK);
	GL_EndRendering ();
	*/
}


